# Copyright 2025 ApeCloud, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# generated by datamodel-codegen:
#   filename:  openapi.merged.yaml
#   timestamp: 2025-09-08T06:55:15+00:00

from __future__ import annotations

from datetime import datetime
from typing import Any, Literal, Optional, Union

from pydantic import BaseModel, ConfigDict, EmailStr, Field, RootModel, confloat, conint


class ModelSpec(BaseModel):
    model: Optional[str] = Field(
        None,
        description='The name of the language model to use',
        examples=['gpt-4o-mini'],
    )
    model_service_provider: Optional[str] = Field(
        None,
        description='Used for querying auth information (api_key/api_base/...) for a model service provider.',
        examples=['openai'],
    )
    custom_llm_provider: Optional[str] = Field(
        None,
        description="Used for Non-OpenAI LLMs (e.g. 'bedrock' for amazon.titan-tg1-large)",
        examples=['openai'],
    )
    temperature: Optional[confloat(ge=0.0, le=2.0)] = Field(
        0.1,
        description='Controls randomness in the output. Values between 0 and 2. Lower values make output more focused and deterministic',
        examples=[0.1],
    )
    max_tokens: Optional[conint(ge=1)] = Field(
        None, description='Maximum number of tokens to generate', examples=[4096]
    )
    max_completion_tokens: Optional[conint(ge=1)] = Field(
        None,
        description='Upper bound for generated completion tokens, including visible and reasoning tokens',
        examples=[4096],
    )
    timeout: Optional[conint(ge=1)] = Field(
        None, description='Maximum execution time in seconds for the API request'
    )
    top_n: Optional[conint(ge=1)] = Field(
        None, description='Number of top results to return when reranking documents'
    )
    tags: Optional[list[str]] = Field(
        [],
        description='Tags for model categorization',
        examples=[['free', 'recommend']],
    )


class CollectionConfig(BaseModel):
    source: Optional[str] = Field(
        None, description='Source system identifier', examples=['system']
    )
    enable_vector: Optional[bool] = Field(
        True, description='Whether to enable vector index'
    )
    enable_fulltext: Optional[bool] = Field(
        True, description='Whether to enable fulltext index'
    )
    enable_knowledge_graph: Optional[bool] = Field(
        True, description='Whether to enable knowledge graph index'
    )
    enable_summary: Optional[bool] = Field(
        False, description='Whether to enable summary index'
    )
    enable_vision: Optional[bool] = Field(
        False, description='Whether to enable vision index'
    )
    embedding: Optional[ModelSpec] = None
    completion: Optional[ModelSpec] = None
    path: Optional[str] = Field(None, description='Path for local and ftp sources')
    host: Optional[str] = Field(None, description='FTP host')
    username: Optional[str] = Field(None, description='FTP username')
    password: Optional[str] = Field(None, description='FTP password')
    region: Optional[str] = Field(None, description='Region for S3/OSS')
    access_key_id: Optional[str] = Field(None, description='Access key ID for S3/OSS')
    secret_access_key: Optional[str] = Field(
        None, description='Secret access key for S3/OSS'
    )
    bucket: Optional[str] = Field(None, description='Bucket name for S3/OSS')
    dir: Optional[str] = Field(None, description='Directory path in bucket for S3/OSS')
    email_source: Optional[dict[str, Any]] = Field(
        None, description='Email source configuration'
    )
    pop_server: Optional[str] = Field(None, description='POP3 server address')
    port: Optional[str] = Field(None, description='Email server port')
    email_address: Optional[str] = Field(None, description='Email address')
    email_password: Optional[str] = Field(None, description='Email password')
    app_id: Optional[str] = Field(None, description='Feishu app ID')
    app_secret: Optional[str] = Field(None, description='Feishu app secret')
    space_id: Optional[str] = Field(None, description='Feishu space ID')


class Local(BaseModel):
    path: Optional[str] = None


class Bucket(BaseModel):
    bucket: Optional[str] = None
    dir: Optional[str] = None


class Oss(BaseModel):
    access_key_id: Optional[str] = None
    access_key_secret: Optional[str] = None
    buckets: Optional[list[Bucket]] = None
    bucket: Optional[str] = None
    endpoint: Optional[str] = None
    region: Optional[str] = None
    dir: Optional[str] = None


class S3(BaseModel):
    access_key_id: Optional[str] = None
    access_key_secret: Optional[str] = None
    buckets: Optional[list[dict[str, Any]]] = None
    bucket: Optional[str] = None
    region: Optional[str] = None
    dir: Optional[str] = None


class Ftp(BaseModel):
    path: Optional[str] = None
    host: Optional[str] = None
    port: Optional[float] = None
    username: Optional[str] = None


class Email(BaseModel):
    pop_server: Optional[str] = None
    port: Optional[float] = None
    email_address: Optional[str] = None
    email_password: Optional[str] = None
    detect_spam: Optional[bool] = None


class Url(BaseModel):
    url: Optional[str] = None
    name: Optional[str] = None


class Feishu(BaseModel):
    app_id: Optional[str] = None
    app_secret: Optional[str] = None
    space_id: Optional[str] = None
    node_id: Optional[str] = None
    method: Optional[str] = None
    target_format: Optional[str] = None


class CollectionSource(BaseModel):
    category: Optional[
        Literal[
            'upload', 'tencent', 'oss', 'local', 's3', 'ftp', 'email', 'url', 'github'
        ]
    ] = None
    upload: Optional[dict[str, Any]] = None
    local: Optional[Local] = None
    oss: Optional[Oss] = None
    s3: Optional[S3] = None
    ftp: Optional[Ftp] = None
    email: Optional[Email] = None
    url: Optional[Url] = None
    feishu: Optional[Feishu] = None


class Collection(BaseModel):
    """
    Collection is a collection of documents
    """

    id: Optional[str] = None
    title: Optional[str] = None
    type: Optional[str] = None
    description: Optional[str] = None
    config: Optional[CollectionConfig] = None
    source: Optional[CollectionSource] = None
    status: Optional[Literal['ACTIVE', 'INACTIVE', 'DELETED']] = None
    created: Optional[datetime] = None
    updated: Optional[datetime] = None
    is_published: Optional[bool] = Field(
        False, description='Whether the collection is published to marketplace'
    )
    published_at: Optional[datetime] = Field(
        None, description='Publication time, null when not published'
    )


class Retry(BaseModel):
    max_attempts: Optional[int] = Field(
        None, description='Maximum number of retry attempts', examples=[3]
    )
    delay: Optional[int] = Field(
        None, description='Delay between retries in seconds', examples=[5]
    )


class Notification(BaseModel):
    email: Optional[list[EmailStr]] = Field(None, examples=[['admin@example.com']])


class ErrorHandling(BaseModel):
    strategy: Optional[Literal['stop_on_error', 'continue_on_error']] = Field(
        None, description='Error handling strategy', examples=['stop_on_error']
    )
    notification: Optional[Notification] = None


class ExecutionConfig(BaseModel):
    """
    Configuration for workflow execution
    """

    timeout: Optional[int] = Field(
        None, description='Overall timeout in seconds', examples=[300]
    )
    retry: Optional[Retry] = None
    error_handling: Optional[ErrorHandling] = None


class SchemaDefinition(BaseModel):
    """
    JSON Schema definition
    """

    model_config = ConfigDict(
        extra='allow',
    )
    type: Optional[
        Literal['object', 'array', 'string', 'number', 'integer', 'boolean']
    ] = None
    properties: Optional[dict[str, Any]] = None
    required: Optional[list[str]] = None
    additionalProperties: Optional[bool] = None


class Input(BaseModel):
    schema_: SchemaDefinition = Field(..., alias='schema')
    values: Optional[dict[str, Any]] = Field(
        None, description='Default values and template references'
    )


class Output(BaseModel):
    schema_: SchemaDefinition = Field(..., alias='schema')


class Data(BaseModel):
    input: Input
    output: Output
    collapsed: Optional[bool] = Field(
        None,
        description='Whether the node is collapsed, only useful for frontend to collapse the node',
        examples=[False],
    )


class Position(BaseModel):
    """
    Position of the node in the frontend
    """

    x: Optional[float] = None
    y: Optional[float] = None


class Measured(BaseModel):
    """
    Measured position of the node, only useful for frontend to measure the node
    """

    width: Optional[float] = None
    height: Optional[float] = None


class Node(BaseModel):
    id: str = Field(
        ...,
        description='Unique identifier for the node',
        examples=['vector_search_3f8e2c1a'],
    )
    ariaLabel: Optional[str] = Field(None, description='label for the node')
    type: Literal[
        'start',
        'vector_search',
        'fulltext_search',
        'graph_search',
        'merge',
        'rerank',
        'llm',
    ] = Field(..., description='Type of node', examples=['vector_search'])
    title: Optional[str] = Field(
        None, description='Human-readable title of the node', examples=['Vector Search']
    )
    data: Data
    position: Optional[Position] = Field(
        None, description='Position of the node in the frontend'
    )
    dragHandle: Optional[str] = Field(
        None,
        description='Drag handle of the node, only useful for frontend to drag the node',
    )
    measured: Optional[Measured] = Field(
        None,
        description='Measured position of the node, only useful for frontend to measure the node',
    )
    selected: Optional[bool] = Field(
        None,
        description='Whether the node is selected, only useful for frontend to select the node',
    )
    deletable: Optional[bool] = Field(
        None,
        description='Whether the node is deletable, only useful for frontend to delete the node',
        examples=[True],
    )


class Edge(BaseModel):
    id: Optional[str] = Field(
        None,
        description='Unique identifier for the edge, only useful for frontend to identify the edge',
        examples=['edge_1'],
    )
    deletable: Optional[bool] = Field(
        None,
        description='Whether the edge is deletable, only useful for frontend to delete the edge',
        examples=[True],
    )
    type: Optional[str] = Field(None, description='Type of the edge', examples=['edge'])
    source: str = Field(..., description='ID of the source node', examples=['start'])
    target: str = Field(
        ..., description='ID of the target node', examples=['vector_search_3f8e2c1a']
    )


class WorkflowStyle(BaseModel):
    """
    Workflow style
    """

    edgeType: Optional[
        Literal['straight', 'step', 'smoothstep', 'default', 'simplebezier']
    ] = None
    layoutDirection: Optional[Literal['TB', 'LR']] = None


class WorkflowDefinition(BaseModel):
    name: str = Field(
        ...,
        description='Machine-readable identifier for the workflow',
        examples=['rag_flow'],
    )
    title: str = Field(
        ...,
        description='Human-readable title of the workflow',
        examples=['RAG Knowledge Base Flow'],
    )
    description: Optional[str] = Field(
        None,
        description='Detailed description of the workflow',
        examples=['A typical RAG flow with parallel retrieval and reranking'],
    )
    version: str = Field(
        ..., description='Version number of the workflow definition', examples=['1.0.0']
    )
    execution: Optional[ExecutionConfig] = None
    schema_: Optional[dict[str, SchemaDefinition]] = Field(
        None,
        alias='schema',
        description='Custom schema definitions used across the workflow',
    )
    nodes: list[Node] = Field(..., description='List of nodes in the workflow')
    edges: list[Edge] = Field(
        ..., description='List of edges connecting nodes in the workflow'
    )
    style: Optional[WorkflowStyle] = None


class Agent(BaseModel):
    completion: Optional[ModelSpec] = None
    system_prompt_template: Optional[str] = None
    query_prompt_template: Optional[str] = None
    collections: Optional[list[Collection]] = None


class BotConfig(BaseModel):
    agent: Optional[Agent] = None
    flow: Optional[WorkflowDefinition] = None


class Bot(BaseModel):
    id: Optional[str] = None
    title: Optional[str] = None
    description: Optional[str] = None
    type: Optional[Literal['knowledge', 'common', 'agent']] = Field(
        None, description='The type of bot', examples=['knowledge']
    )
    config: Optional[BotConfig] = None
    created: Optional[datetime] = None
    updated: Optional[datetime] = None


class PageResult(BaseModel):
    """
    PageResult info (deprecated, use paginatedResponse instead)
    """

    page_number: Optional[int] = Field(None, description='The page number')
    page_size: Optional[int] = Field(None, description='The page size')
    count: Optional[int] = Field(None, description='The total count of items')


class BotList(BaseModel):
    """
    A list of bots
    """

    items: Optional[list[Bot]] = None
    pageResult: Optional[PageResult] = None


class FailResponse(BaseModel):
    code: Optional[str] = Field(None, description='Error code', examples=['400'])
    message: Optional[str] = Field(
        None, description='Error message', examples=['Invalid request']
    )


class BotCreate(BaseModel):
    title: Optional[str] = None
    description: Optional[str] = None
    type: Optional[Literal['knowledge', 'common', 'agent']] = Field(
        None, description='The type of bot', examples=['knowledge']
    )
    config: Optional[BotConfig] = None


class BotUpdate(BaseModel):
    id: Optional[str] = None
    title: Optional[str] = None
    description: Optional[str] = None
    config: Optional[BotConfig] = None


class DebugFlowRequest(BaseModel):
    query: str


class Chat(BaseModel):
    id: Optional[str] = None
    title: Optional[str] = None
    bot_id: Optional[str] = None
    peer_id: Optional[str] = None
    peer_type: Optional[
        Literal['system', 'feishu', 'weixin', 'weixin_official', 'web', 'dingtalk']
    ] = None
    status: Optional[Literal['active', 'archived']] = None
    created: Optional[datetime] = None
    updated: Optional[datetime] = None


class PaginatedResponse(BaseModel):
    total: Optional[conint(ge=0)] = Field(
        None, description='Total number of items', examples=[100]
    )
    page: Optional[conint(ge=1)] = Field(
        None, description='Current page number', examples=[1]
    )
    page_size: Optional[conint(ge=1)] = Field(
        None, description='Number of items per page', examples=[10]
    )
    total_pages: Optional[conint(ge=1)] = Field(
        None, description='Total number of pages', examples=[10]
    )
    has_next: Optional[bool] = Field(
        None, description='Whether there is a next page', examples=[True]
    )
    has_prev: Optional[bool] = Field(
        None, description='Whether there is a previous page', examples=[False]
    )


class ChatList(PaginatedResponse):
    """
    A list of chats with pagination
    """

    items: Optional[list[Chat]] = None


class ChatCreate(BaseModel):
    title: Optional[str] = None


class Reference(BaseModel):
    score: Optional[float] = None
    text: Optional[str] = None
    image_uri: Optional[str] = None
    metadata: Optional[dict[str, Any]] = None


class Feedback(BaseModel):
    type: Optional[Literal['good', 'bad']] = None
    tag: Optional[Literal['Harmful', 'Unsafe', 'Fake', 'Unhelpful', 'Other']] = None
    message: Optional[str] = None


class File(BaseModel):
    id: Optional[str] = None
    name: Optional[str] = None


class ChatMessage(BaseModel):
    id: Optional[str] = None
    part_id: Optional[str] = None
    type: Optional[
        Literal[
            'welcome',
            'message',
            'start',
            'stop',
            'error',
            'tool_call_result',
            'thinking',
            'references',
        ]
    ] = None
    timestamp: Optional[float] = None
    role: Optional[Literal['human', 'ai']] = None
    data: Optional[str] = None
    references: Optional[list[Reference]] = None
    urls: Optional[list[str]] = None
    feedback: Optional[Feedback] = None
    files: Optional[list[File]] = None


class ChatDetails(BaseModel):
    id: Optional[str] = None
    title: Optional[str] = None
    bot_id: Optional[str] = None
    peer_id: Optional[str] = None
    peer_type: Optional[
        Literal['system', 'feishu', 'weixin', 'weixin_official', 'web', 'dingtalk']
    ] = None
    history: Optional[list[list[ChatMessage]]] = Field(
        None,
        description='Array of conversation turns, where each turn is an array of message parts',
    )
    status: Optional[Literal['active', 'archived']] = None
    created: Optional[datetime] = None
    updated: Optional[datetime] = None


class ChatUpdate(BaseModel):
    title: Optional[str] = None


class TitleGenerateRequest(BaseModel):
    max_length: Optional[conint(ge=6, le=50)] = Field(
        20, description='Maximum length of the generated title'
    )
    language: Optional[Literal['zh-CN', 'en-US', 'ja-JP', 'ko-KR']] = Field(
        'zh-CN', description='Language for the title generation (IETF BCP 47 tag)'
    )
    turns: Optional[conint(ge=1)] = Field(
        1, description='Number of most recent conversation turns to consider'
    )


class TitleGenerateResponse(BaseModel):
    title: str = Field(..., description='Generated title string')


class Document(BaseModel):
    id: Optional[str] = None
    name: Optional[str] = None
    status: Optional[
        Literal[
            'UPLOADED',
            'EXPIRED',
            'PENDING',
            'RUNNING',
            'COMPLETE',
            'FAILED',
            'DELETING',
            'DELETED',
        ]
    ] = None
    vector_index_status: Optional[
        Literal[
            'PENDING',
            'CREATING',
            'ACTIVE',
            'DELETING',
            'DELETION_IN_PROGRESS',
            'FAILED',
            'SKIPPED',
        ]
    ] = None
    fulltext_index_status: Optional[
        Literal[
            'PENDING',
            'CREATING',
            'ACTIVE',
            'DELETING',
            'DELETION_IN_PROGRESS',
            'FAILED',
            'SKIPPED',
        ]
    ] = None
    graph_index_status: Optional[
        Literal[
            'PENDING',
            'CREATING',
            'ACTIVE',
            'DELETING',
            'DELETION_IN_PROGRESS',
            'FAILED',
            'SKIPPED',
        ]
    ] = None
    summary_index_status: Optional[
        Literal[
            'PENDING',
            'CREATING',
            'ACTIVE',
            'DELETING',
            'DELETION_IN_PROGRESS',
            'FAILED',
            'SKIPPED',
        ]
    ] = None
    vision_index_status: Optional[
        Literal[
            'PENDING',
            'CREATING',
            'ACTIVE',
            'DELETING',
            'DELETION_IN_PROGRESS',
            'FAILED',
            'SKIPPED',
        ]
    ] = None
    vector_index_updated: Optional[datetime] = Field(
        None, description='Vector index last updated time'
    )
    fulltext_index_updated: Optional[datetime] = Field(
        None, description='Fulltext index last updated time'
    )
    graph_index_updated: Optional[datetime] = Field(
        None, description='Graph index last updated time'
    )
    summary_index_updated: Optional[datetime] = Field(
        None, description='Summary index last updated time'
    )
    vision_index_updated: Optional[datetime] = Field(
        None, description='Vision index last updated time'
    )
    summary: Optional[str] = Field(None, description='Summary of the document')
    size: Optional[float] = None
    created: Optional[datetime] = None
    updated: Optional[datetime] = None


class CollectionView(BaseModel):
    """
    Lightweight collection information for lists, MCP and agents
    """

    id: Optional[str] = None
    title: Optional[str] = None
    description: Optional[str] = None
    type: Optional[str] = None
    status: Optional[Literal['ACTIVE', 'INACTIVE', 'DELETED']] = None
    created: Optional[datetime] = None
    updated: Optional[datetime] = None
    is_published: Optional[bool] = False
    published_at: Optional[datetime] = Field(
        None, description='Publication time, null when not published'
    )
    owner_user_id: Optional[str] = Field(None, description='Collection owner user ID')
    owner_username: Optional[str] = Field(None, description='Collection owner username')
    subscription_id: Optional[str] = Field(
        None,
        description='Subscription ID if this is a subscribed collection, null for owned collections',
    )
    subscribed_at: Optional[datetime] = Field(
        None, description='Subscription time, null for owned collections'
    )


class CollectionViewList(BaseModel):
    """
    A list of collection views
    """

    items: Optional[list[CollectionView]] = None
    pageResult: Optional[PageResult] = None


class CollectionCreate(BaseModel):
    title: Optional[str] = None
    config: Optional[CollectionConfig] = None
    type: Optional[str] = None
    description: Optional[str] = None
    source: Optional[CollectionSource] = None


class CollectionUpdate(BaseModel):
    title: Optional[str] = None
    description: Optional[str] = None
    config: Optional[CollectionConfig] = None
    source: Optional[CollectionSource] = None


class DocumentList(PaginatedResponse):
    """
    A list of documents with pagination
    """

    items: Optional[list[Document]] = None


class DocumentCreate(BaseModel):
    title: Optional[str] = None
    description: Optional[str] = None
    source: Optional[str] = None
    collection_id: Optional[str] = None


class RebuildIndexesRequest(BaseModel):
    index_types: list[Literal['VECTOR', 'FULLTEXT', 'GRAPH', 'SUMMARY', 'VISION']] = (
        Field(..., description='Types of indexes to rebuild', min_length=1)
    )


class VisionChunk(BaseModel):
    id: Optional[str] = None
    asset_id: Optional[str] = None
    text: Optional[str] = None
    metadata: Optional[dict[str, Any]] = None


class Chunk(BaseModel):
    id: Optional[str] = None
    text: Optional[str] = None
    metadata: Optional[dict[str, Any]] = None


class DocumentPreview(BaseModel):
    doc_object_path: Optional[str] = Field(
        None, description='The path to the document object.'
    )
    doc_filename: Optional[str] = Field(None, description='The name of the document.')
    converted_pdf_object_path: Optional[str] = Field(
        None, description='The path to the converted PDF object.'
    )
    markdown_content: Optional[str] = Field(
        None, description='The markdown content of the document.'
    )
    chunks: Optional[list[Chunk]] = None
    vision_chunks: Optional[list[VisionChunk]] = None


class UploadDocumentResponse(BaseModel):
    document_id: str = Field(..., description='ID of the uploaded document')
    filename: str = Field(..., description='Name of the uploaded file')
    size: int = Field(..., description='Size of the uploaded file in bytes')
    status: Literal[
        'UPLOADED', 'PENDING', 'RUNNING', 'COMPLETE', 'FAILED', 'DELETED', 'EXPIRED'
    ] = Field(
        ...,
        description='Status of the document (UPLOADED for new uploads, or existing status for duplicate files)',
    )


class ConfirmDocumentsRequest(BaseModel):
    document_ids: list[str] = Field(
        ..., description='List of document IDs to confirm', min_length=1
    )


class FailedDocument(BaseModel):
    document_id: Optional[str] = None
    name: Optional[str] = Field(None, description='Name of the document')
    error: Optional[str] = None


class ConfirmDocumentsResponse(BaseModel):
    confirmed_count: int = Field(
        ..., description='Number of documents successfully confirmed'
    )
    failed_count: int = Field(
        ..., description='Number of documents that failed to confirm'
    )
    failed_documents: Optional[list[FailedDocument]] = Field(
        None, description='Details of failed confirmations'
    )


class VectorSearchParams(BaseModel):
    topk: Optional[int] = Field(None, description='Top K results')
    similarity: Optional[confloat(ge=0.0, le=1.0)] = Field(
        None, description='Similarity threshold'
    )


class FulltextSearchParams(BaseModel):
    topk: Optional[int] = Field(None, description='Top K results')
    keywords: Optional[list[str]] = Field(
        None, description='Custom keywords to use for fulltext search'
    )


class GraphSearchParams(BaseModel):
    topk: Optional[int] = Field(None, description='Top K results')


class SummarySearchParams(BaseModel):
    topk: Optional[int] = Field(None, description='Top K results')
    similarity: Optional[confloat(ge=0.0, le=1.0)] = Field(
        None, description='Similarity threshold'
    )


class VisionSearchParams(BaseModel):
    topk: Optional[int] = Field(None, description='Top K results')
    similarity: Optional[confloat(ge=0.0, le=1.0)] = Field(
        None, description='Similarity threshold'
    )


class SearchResultItem(BaseModel):
    rank: Optional[int] = Field(None, description='Result rank')
    score: Optional[float] = Field(None, description='Result score')
    content: Optional[str] = Field(None, description='Result content')
    source: Optional[str] = Field(None, description='Source document or metadata')
    recall_type: Optional[
        Literal[
            'vector_search',
            'graph_search',
            'fulltext_search',
            'summary_search',
            'vision_search',
        ]
    ] = Field(None, description='Recall type')
    metadata: Optional[dict[str, Any]] = Field(
        None, description='Metadata of the result'
    )


class SearchResult(BaseModel):
    id: Optional[str] = Field(None, description='The id of the search result')
    query: Optional[str] = None
    vector_search: Optional[VectorSearchParams] = None
    fulltext_search: Optional[FulltextSearchParams] = None
    graph_search: Optional[GraphSearchParams] = None
    summary_search: Optional[SummarySearchParams] = None
    vision_search: Optional[VisionSearchParams] = None
    items: Optional[list[SearchResultItem]] = None
    created: Optional[datetime] = Field(
        None, description='The creation time of the search result'
    )


class SearchResultList(BaseModel):
    """
    A list of search results
    """

    items: Optional[list[SearchResult]] = None


class SearchRequest(BaseModel):
    """
    Search request
    """

    query: Optional[str] = None
    vector_search: Optional[VectorSearchParams] = None
    fulltext_search: Optional[FulltextSearchParams] = None
    graph_search: Optional[GraphSearchParams] = None
    summary_search: Optional[SummarySearchParams] = None
    vision_search: Optional[VisionSearchParams] = None
    save_to_history: Optional[bool] = Field(
        False,
        description='Whether to save search result to database history',
        examples=[True],
    )
    rerank: Optional[bool] = Field(
        False,
        description='Whether to enable rerank for search results',
        examples=[True],
    )


class Settings(BaseModel):
    use_mineru: Optional[bool] = Field(None, description='Whether to use MinerU')
    mineru_api_token: Optional[str] = Field(None, description='API token for MinerU')


class GraphLabelsResponse(BaseModel):
    """
    Response containing available graph labels
    """

    labels: list[str] = Field(
        ...,
        description='List of available node labels in the knowledge graph',
        examples=[['墨香居', '李明华', '林晓雯', '深夜读书会']],
    )


class Properties(BaseModel):
    """
    Node properties containing entity metadata
    """

    model_config = ConfigDict(
        extra='allow',
    )
    entity_id: Optional[str] = Field(
        None, description='Entity identifier', examples=['墨香居']
    )
    entity_type: Optional[str] = Field(
        None, description='Type of the entity', examples=['organization']
    )
    description: Optional[str] = Field(
        None,
        description='Description of the entity',
        examples=[
            '墨香居是这条老巷子里唯一的旧书店，经营着各种书籍，承载了老板李明华的情怀。'
        ],
    )
    source_id: Optional[str] = Field(
        None,
        description='Source chunk ID where entity was extracted',
        examples=['chunk-88845945407136e9498f5f594c8a00c6'],
    )
    file_path: Optional[str] = Field(
        None, description='Source file path', examples=['story.txt']
    )
    created_at: Optional[int] = Field(
        None, description='Creation timestamp', examples=[1751356233]
    )


class GraphNode(BaseModel):
    """
    Knowledge graph node representing an entity
    """

    id: str = Field(
        ...,
        description='Unique identifier for the node (entity name)',
        examples=['墨香居'],
    )
    labels: list[str] = Field(
        ..., description='Labels associated with the node', examples=[['墨香居']]
    )
    properties: Properties = Field(
        ..., description='Node properties containing entity metadata'
    )


class Properties1(BaseModel):
    """
    Edge properties containing relationship metadata
    """

    model_config = ConfigDict(
        extra='allow',
    )
    weight: Optional[float] = Field(
        None, description='Relationship weight/strength', examples=[9]
    )
    description: Optional[str] = Field(
        None,
        description='Description of the relationship',
        examples=['深夜读书会是墨香居的新活动，旨在提升书店的活力和吸引顾客。'],
    )
    keywords: Optional[str] = Field(
        None,
        description='Keywords associated with the relationship',
        examples=['书店活力,活动'],
    )
    source_id: Optional[str] = Field(
        None,
        description='Source chunk ID where relationship was extracted',
        examples=['chunk-88845945407136e9498f5f594c8a00c6'],
    )
    file_path: Optional[str] = Field(
        None, description='Source file path', examples=['story.txt']
    )
    created_at: Optional[int] = Field(
        None, description='Creation timestamp', examples=[1751356233]
    )


class GraphEdge(BaseModel):
    """
    Knowledge graph edge representing a relationship
    """

    id: str = Field(
        ...,
        description='Unique identifier for the edge',
        examples=['墨香居-深夜读书会'],
    )
    type: Optional[str] = Field(
        'DIRECTED', description='Type of the relationship', examples=['DIRECTED']
    )
    source: str = Field(..., description='Source node ID', examples=['墨香居'])
    target: str = Field(..., description='Target node ID', examples=['深夜读书会'])
    properties: Properties1 = Field(
        ..., description='Edge properties containing relationship metadata'
    )


class KnowledgeGraph(BaseModel):
    """
    Knowledge graph containing nodes and edges
    """

    nodes: list[GraphNode] = Field(
        ..., description='List of nodes in the knowledge graph'
    )
    edges: list[GraphEdge] = Field(
        ..., description='List of edges in the knowledge graph'
    )
    is_truncated: bool = Field(
        ...,
        description='Whether the graph was truncated due to size limits',
        examples=[False],
    )


class TargetEntityDataRequest(BaseModel):
    """
    Optional target entity configuration. If not specified, auto-select entity with highest degree.
    """

    entity_name: Optional[str] = Field(
        None,
        description='Target entity name. If not specified, auto-select entity with highest degree',
    )
    entity_type: Optional[str] = Field(
        None, description='Entity type for the target entity'
    )
    description: Optional[str] = Field(
        None, description='Description for the target entity'
    )
    source_id: Optional[str] = Field(
        None, description='Source ID for the target entity'
    )
    file_path: Optional[str] = Field(
        None, description='File path for the target entity'
    )


class NodeMergeRequest(BaseModel):
    """
    Request to merge multiple graph nodes directly using entity IDs.

    """

    model_config = ConfigDict(
        extra='forbid',
    )
    entity_ids: list[str] = Field(
        ...,
        description='List of entity IDs to merge directly',
        examples=[['墨香居', '书店', '旧书店']],
        min_length=1,
    )
    target_entity_data: Optional[TargetEntityDataRequest] = None


class TargetEntityDataResponse(BaseModel):
    """
    Complete data of the target entity after merge
    """

    entity_name: str = Field(
        ...,
        description='The entity name that was kept (merge target)',
        examples=['墨香居'],
    )
    entity_type: str = Field(
        ..., description='Entity type of the target entity', examples=['ORGANIZATION']
    )
    description: str = Field(
        ...,
        description='Merged description of the target entity',
        examples=[
            '墨香居是这条老巷子里唯一的旧书店，经营着各种书籍，承载了老板李明华的情怀。'
        ],
    )
    source_id: Optional[str] = Field(
        None, description='Source ID information', examples=['chunk-001,chunk-002']
    )
    file_path: Optional[str] = Field(
        None, description='File path information', examples=['story.txt,book.txt']
    )


class NodeMergeResponse(BaseModel):
    """
    Response containing node merge results
    """

    status: Literal['success', 'error'] = Field(
        ..., description='Status of the merge operation', examples=['success']
    )
    message: str = Field(
        ...,
        description='Detailed message about the merge operation',
        examples=['Successfully merged 2 entities into 墨香居'],
    )
    entity_ids: list[str] = Field(
        ...,
        description='Entity IDs that were merged',
        examples=[['墨香居', '书店', '旧书店']],
    )
    target_entity_data: TargetEntityDataResponse
    source_entities: list[str] = Field(
        ...,
        description='List of entities that were merged into the target',
        examples=[['书店', '旧书店']],
    )
    redirected_edges: conint(ge=0) = Field(
        ...,
        description='Number of edges that were redirected during merge',
        examples=[12],
    )
    merged_description_length: conint(ge=0) = Field(
        ..., description='Length of the merged description', examples=[512]
    )
    suggestion_id: Optional[str] = Field(
        None,
        description='Suggestion ID if this merge was based on a suggestion',
        examples=['msug123'],
    )


class MergeSuggestionsRequest(BaseModel):
    """
    Request for generating node merge suggestions
    """

    max_suggestions: Optional[conint(ge=1, le=100)] = Field(
        50, description='Maximum number of merge suggestions to return', examples=[50]
    )
    max_concurrent_llm_calls: Optional[conint(ge=1, le=16)] = Field(
        4,
        description='Maximum number of concurrent LLM calls for batch analysis',
        examples=[4],
    )
    force_refresh: Optional[bool] = Field(
        False,
        description='Force regeneration of suggestions even if valid cached suggestions exist',
        examples=[False],
    )


class MergeSuggestionTargetEntity(BaseModel):
    """
    Suggested target entity for merge
    """

    entity_name: str = Field(
        ..., description='Suggested entity name after merge', examples=['墨香居']
    )
    entity_type: str = Field(
        ..., description='Suggested entity type after merge', examples=['ORGANIZATION']
    )


class MergeSuggestionItem(BaseModel):
    """
    Individual merge suggestion item
    """

    id: str = Field(..., description='Suggestion ID', examples=['msug123'])
    collection_id: str = Field(..., description='Collection ID', examples=['col123'])
    suggestion_batch_id: str = Field(
        ..., description='Suggestion batch ID', examples=['batch456']
    )
    entity_ids: list[str] = Field(
        ...,
        description='Entity IDs suggested for merging',
        examples=[['墨香居', '书店', '旧书店']],
    )
    confidence_score: confloat(ge=0.0, le=1.0) = Field(
        ...,
        description='LLM confidence score for this merge suggestion',
        examples=[0.85],
    )
    merge_reason: str = Field(
        ...,
        description='LLM-generated reason for suggesting this merge',
        examples=[
            "两个实体都描述同一个书店，'墨香居'是具体名称，'旧书店'是通用描述，应该合并为同一实体"
        ],
    )
    suggested_target_entity: MergeSuggestionTargetEntity
    status: Literal['PENDING', 'ACCEPTED', 'REJECTED', 'EXPIRED'] = Field(
        ..., description='Status of the suggestion', examples=['PENDING']
    )
    created: datetime = Field(
        ..., description='Creation timestamp', examples=['2025-01-07T10:00:00Z']
    )
    operated_at: Optional[datetime] = Field(
        None, description='User operation timestamp', examples=['2025-01-08T15:30:00Z']
    )


class MergeSuggestionsResponse(BaseModel):
    """
    Response containing node merge suggestions
    """

    suggestions: list[MergeSuggestionItem] = Field(
        ...,
        description='List of merge suggestions ordered by confidence score (highest first)',
    )
    total_analyzed_nodes: conint(ge=0) = Field(
        ..., description='Total number of nodes analyzed', examples=[156]
    )
    processing_time_seconds: confloat(ge=0.0) = Field(
        ..., description='Processing time in seconds', examples=[12.5]
    )
    from_cache: bool = Field(
        ..., description='Whether suggestions were loaded from cache', examples=[False]
    )
    generated_at: datetime = Field(
        ..., description='Generation timestamp', examples=['2025-01-07T10:00:00Z']
    )
    total_suggestions: conint(ge=0) = Field(
        ..., description='Total number of suggestions', examples=[5]
    )
    pending_count: conint(ge=0) = Field(
        ..., description='Number of pending suggestions', examples=[3]
    )
    accepted_count: conint(ge=0) = Field(
        ..., description='Number of accepted suggestions', examples=[1]
    )
    rejected_count: conint(ge=0) = Field(
        ..., description='Number of rejected suggestions', examples=[1]
    )


class SuggestionActionRequest(BaseModel):
    """
    Request to take action on a merge suggestion
    """

    model_config = ConfigDict(
        extra='forbid',
    )
    action: Literal['accept', 'reject'] = Field(
        ...,
        description="Action to take on the suggestion (case-insensitive, e.g., 'Accept', 'REJECT', 'accept')",
        examples=['accept'],
    )
    target_entity_data: Optional[TargetEntityDataRequest] = Field(
        None,
        description="Optional override for target entity data (only used when action is 'accept')",
    )


class SuggestionActionResponse(BaseModel):
    """
    Response containing suggestion action results
    """

    status: Literal['success', 'error'] = Field(
        ..., description='Status of the action operation', examples=['success']
    )
    message: str = Field(
        ...,
        description='Detailed message about the action operation',
        examples=['Suggestion msug123 has been accepted and merge completed'],
    )
    suggestion_id: str = Field(
        ..., description='The suggestion ID that was processed', examples=['msug123']
    )
    action: Literal['accept', 'reject'] = Field(
        ...,
        description='The action that was performed (normalized to lowercase)',
        examples=['accept'],
    )
    merge_result: Optional[NodeMergeResponse] = Field(
        None,
        description="Merge operation result (only present when action is 'accept')",
    )


class SharingStatusResponse(BaseModel):
    """
    Simple sharing status response
    """

    is_published: bool = Field(..., description='Whether published to marketplace')
    published_at: Optional[datetime] = Field(
        None, description='Publication time, null when not published'
    )


class SharedCollectionConfig(BaseModel):
    """
    Configuration settings for shared collection features
    """

    enable_vector: bool = Field(..., description='Whether vector search is enabled')
    enable_fulltext: bool = Field(..., description='Whether fulltext search is enabled')
    enable_knowledge_graph: bool = Field(
        ..., description='Whether knowledge graph is enabled'
    )
    enable_summary: bool = Field(
        ..., description='Whether summary generation is enabled'
    )
    enable_vision: bool = Field(..., description='Whether vision processing is enabled')


class SharedCollection(BaseModel):
    """
    Shared Collection information for marketplace users
    """

    id: str = Field(..., description='Collection ID')
    title: str = Field(..., description='Collection title')
    description: Optional[str] = Field(None, description='Collection description')
    owner_user_id: str = Field(..., description='Original owner user ID')
    owner_username: Optional[str] = Field(None, description='Original owner username')
    subscription_id: Optional[str] = Field(
        None,
        description='Subscription record ID (has value if subscribed, null if not subscribed)',
    )
    gmt_subscribed: Optional[datetime] = Field(
        None, description='Subscription time (only has value when subscribed)'
    )
    config: SharedCollectionConfig = Field(
        ..., description='Collection configuration settings'
    )


class SharedCollectionList(BaseModel):
    """
    Shared Collection list response
    """

    items: list[SharedCollection] = Field(..., description='List of shared Collections')
    total: int = Field(..., description='Total count (for pagination)')
    page: int = Field(..., description='Current page number')
    page_size: int = Field(..., description='Page size')


class ApiKey(BaseModel):
    id: Optional[str] = None
    key: Optional[str] = None
    description: Optional[str] = None
    created_at: Optional[datetime] = None
    updated_at: Optional[datetime] = None
    last_used_at: Optional[datetime] = None


class ApiKeyList(BaseModel):
    """
    A list of API keys
    """

    items: Optional[list[ApiKey]] = None
    pageResult: Optional[PageResult] = None


class ApiKeyCreate(BaseModel):
    description: Optional[str] = None


class ApiKeyUpdate(BaseModel):
    description: Optional[str] = None


class TagFilterCondition(BaseModel):
    operation: Literal['AND', 'OR'] = Field(
        ...,
        description='Logical operation for tags in this condition',
        examples=['AND'],
    )
    tags: list[str] = Field(
        ...,
        description='List of tags for this condition',
        examples=[['free', 'recommend']],
    )


class TagFilterRequest(BaseModel):
    """
    Tag filtering request. Empty request body or empty tag_filters returns recommend models by default.
    """

    tag_filters: Optional[list[TagFilterCondition]] = Field(
        None,
        description='List of tag filter conditions (OR relationship between conditions). If not provided or empty, returns models with "recommend" tag by default.',
        examples=[
            [
                {'operation': 'AND', 'tags': ['free', 'recommend']},
                {'operation': 'OR', 'tags': ['openai', 'gpt']},
            ]
        ],
    )


class ModelConfig(BaseModel):
    name: Optional[str] = None
    completion_dialect: Optional[str] = None
    embedding_dialect: Optional[str] = None
    rerank_dialect: Optional[str] = None
    label: Optional[str] = None
    allow_custom_base_url: Optional[bool] = None
    base_url: Optional[str] = None
    embedding: Optional[list[ModelSpec]] = None
    completion: Optional[list[ModelSpec]] = None
    rerank: Optional[list[ModelSpec]] = None


class ModelConfigList(BaseModel):
    items: Optional[list[ModelConfig]] = None
    pageResult: Optional[PageResult] = None


class DefaultModelConfig(BaseModel):
    scenario: Literal[
        'default_for_collection_completion',
        'default_for_agent_completion',
        'default_for_embedding',
        'default_for_rerank',
        'default_for_background_task',
    ] = Field(
        ...,
        description='The scenario for which this default model is configured',
        examples=['default_for_embedding'],
    )
    custom_llm_provider: Optional[str] = None
    provider_name: Optional[str] = Field(
        None, description='The name of the model provider', examples=['openai']
    )
    model: Optional[str] = Field(
        None, description='The name of the model', examples=['text-embedding-3-small']
    )


class DefaultModelsResponse(BaseModel):
    items: list[DefaultModelConfig] = Field(
        ..., description='List of default model configurations for different scenarios'
    )


class DefaultModelsUpdateRequest(BaseModel):
    defaults: list[DefaultModelConfig] = Field(
        ...,
        description='List of default model configurations to update',
        examples=[
            [
                {
                    'scenario': 'default_for_embedding',
                    'provider_name': 'openai',
                    'model': 'text-embedding-3-small',
                },
                {
                    'scenario': 'default_for_collection_completion',
                    'provider_name': 'openai',
                    'model': 'gpt-4o-mini',
                },
            ]
        ],
    )


class LlmProvider(BaseModel):
    name: str = Field(
        ..., description='Unique provider name identifier', examples=['openai']
    )
    user_id: str = Field(
        ...,
        description='User ID of the provider owner, "public" for system providers',
        examples=['public'],
    )
    label: str = Field(
        ..., description='Human-readable provider display name', examples=['OpenAI']
    )
    completion_dialect: Optional[str] = Field(
        'openai',
        description='API dialect for completion/chat APIs',
        examples=['openai'],
    )
    embedding_dialect: Optional[str] = Field(
        'openai', description='API dialect for embedding APIs', examples=['openai']
    )
    rerank_dialect: Optional[str] = Field(
        'jina_ai', description='API dialect for rerank APIs', examples=['jina_ai']
    )
    allow_custom_base_url: Optional[bool] = Field(
        False, description='Whether custom base URLs are allowed'
    )
    base_url: str = Field(
        ...,
        description='Default API base URL for this provider',
        examples=['https://api.openai.com/v1'],
    )
    extra: Optional[str] = Field(
        None, description='Additional configuration data in JSON format'
    )
    api_key: Optional[str] = Field(
        None, description='API key for this provider (if configured by user)'
    )
    created: Optional[datetime] = Field(None, description='Creation timestamp')
    updated: Optional[datetime] = Field(None, description='Last update timestamp')


class LlmProviderModel(BaseModel):
    provider_name: str = Field(
        ..., description='Reference to LLMProvider.name', examples=['openai']
    )
    api: Literal['completion', 'embedding', 'rerank'] = Field(
        ..., description='API type for this model', examples=['completion']
    )
    model: str = Field(
        ..., description='Model name/identifier', examples=['gpt-4o-mini']
    )
    custom_llm_provider: str = Field(
        ..., description='Custom LLM provider implementation', examples=['openai']
    )
    context_window: Optional[int] = Field(
        None, description='Context window size (total tokens)', examples=[128000]
    )
    max_input_tokens: Optional[int] = Field(
        None, description='Maximum input tokens', examples=[120000]
    )
    max_output_tokens: Optional[int] = Field(
        None, description='Maximum output tokens', examples=[8000]
    )
    tags: Optional[list[str]] = Field(
        [],
        description='Tags for model categorization',
        examples=[['free', 'recommend']],
    )
    created: Optional[datetime] = Field(None, description='Creation timestamp')
    updated: Optional[datetime] = Field(None, description='Last update timestamp')


class LlmConfigurationResponse(BaseModel):
    providers: list[LlmProvider] = Field(..., description='List of LLM providers')
    models: list[LlmProviderModel] = Field(
        ..., description='List of LLM provider models'
    )


class LlmProviderCreateWithApiKey(BaseModel):
    name: Optional[str] = Field(
        None,
        description='Unique provider name identifier (auto-generated if not provided)',
    )
    label: str = Field(..., description='Human-readable provider display name')
    completion_dialect: Optional[str] = Field(
        'openai', description='API dialect for completion/chat APIs'
    )
    embedding_dialect: Optional[str] = Field(
        'openai', description='API dialect for embedding APIs'
    )
    rerank_dialect: Optional[str] = Field(
        'jina_ai', description='API dialect for rerank APIs'
    )
    allow_custom_base_url: Optional[bool] = Field(
        False, description='Whether custom base URLs are allowed'
    )
    base_url: str = Field(..., description='Default API base URL for this provider')
    extra: Optional[str] = Field(
        None, description='Additional configuration data in JSON format'
    )
    api_key: Optional[str] = Field(
        None, description='Optional API key for this provider'
    )
    status: Optional[Literal['enable', 'disable']] = Field(
        None,
        description='Provider status - enable to create/update API key, disable to remove API key',
    )


class LlmProviderUpdateWithApiKey(BaseModel):
    label: Optional[str] = Field(
        None, description='Human-readable provider display name'
    )
    completion_dialect: Optional[str] = Field(
        None, description='API dialect for completion/chat APIs'
    )
    embedding_dialect: Optional[str] = Field(
        None, description='API dialect for embedding APIs'
    )
    rerank_dialect: Optional[str] = Field(
        None, description='API dialect for rerank APIs'
    )
    allow_custom_base_url: Optional[bool] = Field(
        None, description='Whether custom base URLs are allowed'
    )
    base_url: Optional[str] = Field(
        None, description='Default API base URL for this provider'
    )
    extra: Optional[str] = Field(
        None, description='Additional configuration data in JSON format'
    )
    api_key: Optional[str] = Field(
        None, description='Optional API key for this provider'
    )
    status: Optional[Literal['enable', 'disable']] = Field(
        None,
        description='Provider status - enable to create/update API key, disable to remove API key',
    )


class LlmProviderModelList(BaseModel):
    items: Optional[list[LlmProviderModel]] = None
    pageResult: Optional[PageResult] = None


class LlmProviderModelCreate(BaseModel):
    provider_name: str = Field(..., description='Reference to LLMProvider.name')
    api: Literal['completion', 'embedding', 'rerank'] = Field(
        ..., description='API type for this model'
    )
    model: str = Field(..., description='Model name/identifier')
    custom_llm_provider: str = Field(
        ..., description='Custom LLM provider implementation'
    )
    context_window: Optional[int] = Field(
        None, description='Context window size (total tokens)', examples=[128000]
    )
    max_input_tokens: Optional[int] = Field(
        None, description='Maximum input tokens', examples=[120000]
    )
    max_output_tokens: Optional[int] = Field(
        None, description='Maximum output tokens', examples=[8000]
    )
    tags: Optional[list[str]] = Field([], description='Tags for model categorization')


class LlmProviderModelUpdate(BaseModel):
    custom_llm_provider: Optional[str] = Field(
        None, description='Custom LLM provider implementation'
    )
    context_window: Optional[int] = Field(
        None, description='Context window size (total tokens)', examples=[128000]
    )
    max_input_tokens: Optional[int] = Field(
        None, description='Maximum input tokens', examples=[120000]
    )
    max_output_tokens: Optional[int] = Field(
        None, description='Maximum output tokens', examples=[8000]
    )
    tags: Optional[list[str]] = Field(None, description='Tags for model categorization')


class EmbeddingRequest(BaseModel):
    """
    Request to generate embeddings for text inputs
    """

    provider: str = Field(
        ...,
        description='LLM provider name (e.g., openai, anthropic)',
        examples=['openai'],
    )
    model: str = Field(
        ...,
        description='Model name for embedding generation',
        examples=['text-embedding-3-small'],
    )
    input: Union[str, list[str]]


class EmbeddingData(BaseModel):
    """
    Individual embedding result
    """

    object: str = Field(
        ..., description='Object type identifier', examples=['embedding']
    )
    embedding: list[float] = Field(
        ...,
        description='The embedding vector as a list of floats',
        examples=[[0.0023064255, -0.009327292, 0.015797421, 0.0012345678]],
    )
    index: int = Field(
        ...,
        description='Index of the input text corresponding to this embedding',
        examples=[0],
    )


class EmbeddingUsage(BaseModel):
    """
    Token usage information for the embedding request
    """

    prompt_tokens: int = Field(
        ..., description='Number of tokens in the input text(s)', examples=[16]
    )
    total_tokens: int = Field(
        ...,
        description='Total number of tokens used (same as prompt_tokens for embeddings)',
        examples=[16],
    )


class EmbeddingResponse(BaseModel):
    """
    Response containing generated embeddings in OpenAI-compatible format
    """

    object: str = Field(..., description='Object type identifier', examples=['list'])
    data: list[EmbeddingData] = Field(..., description='List of embedding results')
    model: str = Field(
        ...,
        description='Model used for embedding generation',
        examples=['text-embedding-3-small'],
    )
    usage: EmbeddingUsage


class Document1(BaseModel):
    text: str = Field(
        ...,
        description='Document text content',
        examples=['Paris is the capital of France.'],
    )
    metadata: Optional[dict[str, Any]] = Field(
        None,
        description='Optional document metadata',
        examples=[{'id': 'doc_123', 'source': 'wikipedia'}],
    )


class RerankRequest(BaseModel):
    """
    Request to rerank documents based on query relevance
    """

    provider: str = Field(
        ...,
        description='LLM provider name (e.g., cohere, jina_ai)',
        examples=['cohere'],
    )
    model: str = Field(
        ..., description='Model name for reranking', examples=['rerank-english-v3.0']
    )
    query: str = Field(
        ...,
        description='Search query to rank documents against',
        examples=['What is the capital of France?'],
    )
    documents: Union[list[str], list[Document1]]
    top_k: Optional[conint(ge=1, le=1000)] = Field(
        10, description='Maximum number of top-ranked documents to return', examples=[3]
    )
    return_documents: Optional[bool] = Field(
        True,
        description='Whether to return document content in response',
        examples=[True],
    )


class Document2(BaseModel):
    """
    Document content and metadata (only present if return_documents=true)
    """

    text: str = Field(
        ...,
        description='Document text content',
        examples=['Paris is the capital of France.'],
    )
    metadata: Optional[dict[str, Any]] = Field(
        None,
        description='Document metadata if provided in the request',
        examples=[{'id': 'doc_123', 'source': 'wikipedia'}],
    )


class RerankDocument(BaseModel):
    """
    Individual reranked document result
    """

    index: int = Field(
        ...,
        description='Original index of the document in the input array',
        examples=[0],
    )
    relevance_score: confloat(ge=0.0, le=1.0) = Field(
        ...,
        description='Relevance score between 0 and 1 (higher is more relevant)',
        examples=[0.95],
    )
    document: Optional[Document2] = Field(
        None,
        description='Document content and metadata (only present if return_documents=true)',
    )


class RerankUsage(BaseModel):
    """
    Token usage information for the rerank request
    """

    total_tokens: int = Field(
        ...,
        description='Total number of tokens processed (query + all documents)',
        examples=[156],
    )


class RerankResponse(BaseModel):
    """
    Response containing reranked documents in industry-standard format
    """

    object: str = Field(..., description='Object type identifier', examples=['list'])
    data: list[RerankDocument] = Field(
        ...,
        description='List of reranked documents ordered by relevance (highest first)',
    )
    model: str = Field(
        ..., description='Model used for reranking', examples=['rerank-english-v3.0']
    )
    usage: RerankUsage


class Auth0(BaseModel):
    auth_domain: Optional[str] = None
    auth_app_id: Optional[str] = None


class Authing(BaseModel):
    auth_domain: Optional[str] = None
    auth_app_id: Optional[str] = None


class Logto(BaseModel):
    auth_domain: Optional[str] = None
    auth_app_id: Optional[str] = None


class Auth(BaseModel):
    type: Optional[Literal['none', 'auth0', 'authing', 'logto', 'cookie']] = None
    auth0: Optional[Auth0] = None
    authing: Optional[Authing] = None
    logto: Optional[Logto] = None


class Config(BaseModel):
    admin_user_exists: Optional[bool] = Field(
        None, description='Whether the admin user exists'
    )
    auth: Optional[Auth] = None
    login_methods: Optional[list[str]] = Field(
        None,
        description='Available login methods',
        examples=[['local', 'google', 'github']],
    )


class PromptTemplate(BaseModel):
    name: Optional[str] = None
    prompt: Optional[str] = None
    description: Optional[str] = None


class PromptTemplateList(BaseModel):
    """
    A list of prompt templates
    """

    items: Optional[list[PromptTemplate]] = None
    pageResult: Optional[PageResult] = None


class AuditLog(BaseModel):
    """
    Audit log entry
    """

    id: Optional[str] = Field(None, description='Audit log ID')
    user_id: Optional[str] = Field(None, description='User ID who performed the action')
    username: Optional[str] = Field(None, description='Username for display')
    resource_type: Optional[
        Literal[
            'collection',
            'document',
            'bot',
            'chat',
            'message',
            'api_key',
            'llm',
            'llm_provider',
            'llm_provider_model',
            'model_service_provider',
            'user',
            'flow',
            'search',
            'index',
        ]
    ] = Field(None, description='Type of resource')
    resource_id: Optional[str] = Field(
        None, description='ID of the resource (extracted at query time)'
    )
    api_name: Optional[str] = Field(None, description='API operation name')
    http_method: Optional[str] = Field(
        None, description='HTTP method (POST, PUT, DELETE)'
    )
    path: Optional[str] = Field(None, description='API path')
    status_code: Optional[int] = Field(None, description='HTTP status code')
    start_time: Optional[int] = Field(
        None, description='Request start time (milliseconds since epoch)'
    )
    end_time: Optional[int] = Field(
        None, description='Request end time (milliseconds since epoch)'
    )
    duration_ms: Optional[int] = Field(
        None, description='Request duration in milliseconds (calculated)'
    )
    request_data: Optional[str] = Field(None, description='Request data (JSON string)')
    response_data: Optional[str] = Field(
        None, description='Response data (JSON string)'
    )
    error_message: Optional[str] = Field(None, description='Error message if failed')
    ip_address: Optional[str] = Field(None, description='Client IP address')
    user_agent: Optional[str] = Field(None, description='User agent string')
    request_id: Optional[str] = Field(None, description='Request ID for tracking')
    created: Optional[datetime] = Field(None, description='Created timestamp')


class AuditLogList(PaginatedResponse):
    """
    List of audit logs with pagination
    """

    items: Optional[list[AuditLog]] = Field(None, description='Audit log entries')


class InvitationCreate(BaseModel):
    username: Optional[str] = Field(None, description='The username of the user')
    email: Optional[str] = Field(None, description='The email of the user')
    role: Optional[Literal['admin', 'rw', 'ro']] = Field(
        None, description='The role of the user (admin, rw, ro)'
    )


class Invitation(BaseModel):
    email: Optional[str] = Field(None, description='The email of the user')
    token: Optional[str] = Field(None, description='The token of the invitation')
    created_by: Optional[str] = Field(
        None, description='The ID of the user who created the invitation'
    )
    created_at: Optional[str] = Field(
        None, description='The date and time the invitation was created'
    )
    is_valid: Optional[bool] = Field(
        None, description='Whether the invitation is valid'
    )
    used_at: Optional[str] = Field(
        None, description='The date and time the invitation was used'
    )
    role: Optional[Literal['admin', 'rw', 'ro']] = Field(
        None, description='The role of the user (admin, rw, ro)'
    )
    expires_at: Optional[str] = Field(
        None, description='The date and time the invitation will expire'
    )


class InvitationList(BaseModel):
    """
    A list of invitations
    """

    items: Optional[list[Invitation]] = None
    pageResult: Optional[PageResult] = None


class Register(BaseModel):
    """
    The email of the user
    """

    token: Optional[str] = Field(None, description='The invitation token')
    email: Optional[str] = Field(None, description='The email of the user')
    username: Optional[str] = Field(None, description='The username of the user')
    password: Optional[str] = Field(None, description='The password of the user')


class User(BaseModel):
    id: Optional[str] = Field(None, description='The ID of the user')
    username: Optional[str] = Field(None, description='The username of the user')
    email: Optional[str] = Field(None, description='The email of the user')
    role: Optional[str] = Field(None, description='The role of the user')
    is_active: Optional[bool] = Field(None, description='Whether the user is active')
    date_joined: Optional[str] = Field(
        None, description='The date and time the user joined the system'
    )
    registration_source: Optional[str] = Field(
        None,
        description='The registration source of the user (local, google, github, etc.)',
    )


class Login(BaseModel):
    username: Optional[str] = Field(None, description='The username of the user')
    password: Optional[str] = Field(None, description='The password of the user')


class UserList(BaseModel):
    """
    A list of users
    """

    items: Optional[list[User]] = None
    pageResult: Optional[PageResult] = None


class ChangePassword(BaseModel):
    username: Optional[str] = Field(None, description='The username of the user')
    old_password: Optional[str] = Field(
        None, description='The old password of the user'
    )
    new_password: Optional[str] = Field(
        None, description='The new password of the user'
    )


class QuotaInfo(BaseModel):
    """
    Quota information for a specific quota type
    """

    quota_type: str = Field(
        ..., description='Type of quota', examples=['max_collection_count']
    )
    quota_limit: int = Field(..., description='Maximum allowed usage', examples=[10])
    current_usage: int = Field(..., description='Current usage count', examples=[3])
    remaining: int = Field(..., description='Remaining quota available', examples=[7])


class UserQuotaInfo(BaseModel):
    """
    Complete quota information for a user
    """

    user_id: str = Field(..., description='User ID', examples=['user123'])
    username: Optional[str] = Field(None, description='Username', examples=['john_doe'])
    email: Optional[str] = Field(
        None, description='User email', examples=['john@example.com']
    )
    role: str = Field(..., description='User role', examples=['rw'])
    quotas: list[QuotaInfo] = Field(..., description='List of quota information')


class UserQuotaList(BaseModel):
    """
    List of user quota information (admin view)
    """

    items: list[UserQuotaInfo] = Field(
        ..., description='List of user quota information'
    )


class QuotaUpdateRequest(BaseModel):
    """
    Request to update user quotas (supports both single and batch updates)
    """

    max_collection_count: Optional[conint(ge=0)] = Field(
        None, description='New limit for collection count'
    )
    max_document_count: Optional[conint(ge=0)] = Field(
        None, description='New limit for document count'
    )
    max_document_count_per_collection: Optional[conint(ge=0)] = Field(
        None, description='New limit for documents per collection'
    )
    max_bot_count: Optional[conint(ge=0)] = Field(
        None, description='New limit for bot count'
    )


class UpdatedQuota(BaseModel):
    quota_type: str = Field(
        ...,
        description='Type of quota that was updated',
        examples=['max_collection_count'],
    )
    old_limit: int = Field(..., description='Previous quota limit', examples=[10])
    new_limit: int = Field(..., description='New quota limit', examples=[20])


class QuotaUpdateResponse(BaseModel):
    """
    Response after updating user quotas (supports both single and batch updates)
    """

    success: bool = Field(
        ..., description='Whether the update was successful', examples=[True]
    )
    message: str = Field(
        ..., description='Status message', examples=['Quotas updated successfully']
    )
    user_id: str = Field(
        ..., description='User ID that was updated', examples=['user123']
    )
    updated_quotas: list[UpdatedQuota] = Field(
        ..., description='List of updated quotas'
    )


class SystemDefaultQuotas(BaseModel):
    """
    System default quota configuration
    """

    max_collection_count: conint(ge=0) = Field(
        ..., description='Default maximum collection count', examples=[10]
    )
    max_document_count: conint(ge=0) = Field(
        ..., description='Default maximum document count', examples=[1000]
    )
    max_document_count_per_collection: conint(ge=0) = Field(
        ..., description='Default maximum documents per collection', examples=[100]
    )
    max_bot_count: conint(ge=0) = Field(
        ..., description='Default maximum bot count', examples=[5]
    )


class SystemDefaultQuotasResponse(BaseModel):
    """
    Response containing system default quotas
    """

    quotas: SystemDefaultQuotas


class SystemDefaultQuotasUpdateRequest(BaseModel):
    """
    Request to update system default quotas
    """

    quotas: SystemDefaultQuotas


class SystemDefaultQuotasUpdateResponse(BaseModel):
    """
    Response after updating system default quotas
    """

    success: bool = Field(
        ..., description='Whether the update was successful', examples=[True]
    )
    message: str = Field(
        ...,
        description='Status message',
        examples=['System default quotas updated successfully'],
    )
    quotas: SystemDefaultQuotas


class WebSearchRequest(BaseModel):
    """
    Web search request
    """

    query: Optional[str] = Field(
        None,
        description='Search query for regular web search. Optional if only using LLM.txt discovery.',
        examples=['ApeRAG 2025年最新发展'],
    )
    max_results: Optional[int] = Field(
        5, description='Maximum number of results to return', examples=[5]
    )
    timeout: Optional[int] = Field(
        30, description='Request timeout in seconds', examples=[30]
    )
    locale: Optional[str] = Field(
        'en-US', description='Browser locale', examples=['en-US']
    )
    source: Optional[str] = Field(
        None,
        description="Domain or URL for site-specific filtering. When provided with query, limits search results to this domain (e.g., 'site:vercel.com query').",
        examples=['vercel.com'],
    )
    search_llms_txt: Optional[str] = Field(
        None,
        description='Domain for LLM.txt discovery search. When provided, performs additional LLM-optimized content discovery from the specified domain, independent of the main search. Results are merged with regular search results.',
        examples=['anthropic.com'],
    )


class WebSearchResultItem(BaseModel):
    """
    Individual web search result
    """

    rank: int = Field(..., description='Result rank', examples=[1])
    title: str = Field(
        ..., description='Page title', examples=['ApeRAG 2025年技术路线图']
    )
    url: str = Field(
        ...,
        description='Page URL',
        examples=['https://example.com/aperag-2025-roadmap'],
    )
    snippet: str = Field(
        ..., description='Page snippet', examples=['ApeRAG在2025年将重点发展...']
    )
    domain: str = Field(..., description='Domain name', examples=['example.com'])
    timestamp: Optional[datetime] = Field(
        None, description='Result timestamp', examples=['2025-01-01T00:00:00Z']
    )


class WebSearchResponse(BaseModel):
    """
    Web search response
    """

    query: str = Field(..., description='Original search query')
    results: list[WebSearchResultItem] = Field(
        ..., description='List of search results'
    )
    total_results: Optional[int] = Field(
        None, description='Total number of results found'
    )
    search_time: Optional[float] = Field(None, description='Search time in seconds')


class WebReadRequest(BaseModel):
    """
    Web content reading request
    """

    url_list: list[str] = Field(
        ...,
        description='List of URLs to read (for single URL, use array with one element)',
        examples=[['https://example.com/article']],
    )
    timeout: Optional[int] = Field(
        30, description='Request timeout in seconds', examples=[30]
    )
    locale: Optional[str] = Field(
        'en-US', description='Browser locale', examples=['en-US']
    )
    max_concurrent: Optional[int] = Field(
        3, description='Maximum concurrent requests for multiple URLs', examples=[3]
    )


class WebReadResultItem(BaseModel):
    """
    Individual web content reading result
    """

    url: str = Field(..., description='Requested URL')
    status: Literal['success', 'error'] = Field(..., description='Processing status')
    title: Optional[str] = Field(None, description='Page title')
    content: Optional[str] = Field(
        None, description='Extracted content in Markdown format'
    )
    extracted_at: Optional[datetime] = Field(
        None, description='Content extraction timestamp'
    )
    word_count: Optional[int] = Field(None, description='Word count of content')
    token_count: Optional[int] = Field(None, description='Estimated token count')
    error: Optional[str] = Field(None, description='Error message if failed')
    error_code: Optional[str] = Field(None, description='Error code if failed')


class WebReadResponse(BaseModel):
    """
    Web content reading response
    """

    results: list[WebReadResultItem] = Field(..., description='List of reading results')
    total_urls: int = Field(..., description='Total number of URLs processed')
    successful: int = Field(..., description='Number of successful extractions')
    failed: int = Field(..., description='Number of failed extractions')
    processing_time: Optional[float] = Field(
        None, description='Total processing time in seconds'
    )


class QuestionSet(BaseModel):
    id: Optional[str] = None
    user_id: Optional[str] = None
    collection_id: Optional[str] = None
    name: Optional[str] = None
    description: Optional[str] = None
    gmt_created: Optional[datetime] = None
    gmt_updated: Optional[datetime] = None


class QuestionSetList(BaseModel):
    items: Optional[list[QuestionSet]] = None
    total: Optional[int] = None
    page: Optional[int] = None
    page_size: Optional[int] = None


class QuestionType(RootModel[Literal['FACTUAL', 'INFERENTIAL', 'USER_DEFINED']]):
    root: Literal['FACTUAL', 'INFERENTIAL', 'USER_DEFINED'] = Field(
        ..., description='Question type enumeration'
    )


class Question(BaseModel):
    id: Optional[str] = None
    question_set_id: Optional[str] = None
    question_type: Optional[QuestionType] = None
    question_text: Optional[str] = None
    ground_truth: Optional[str] = None
    gmt_created: Optional[datetime] = None
    gmt_updated: Optional[datetime] = None


class QuestionSetCreate(BaseModel):
    name: str
    description: Optional[str] = None
    collection_id: Optional[str] = None
    questions: Optional[list[Question]] = Field(
        None, description='A list of questions. Maximum 1000 questions are allowed.'
    )


class LLMConfig(BaseModel):
    model_name: Optional[str] = None
    model_service_provider: Optional[str] = None
    custom_llm_provider: Optional[str] = None


class QuestionSetGenerate(BaseModel):
    collection_id: str
    llm_config: Optional[LLMConfig] = None
    question_count: Optional[int] = None
    prompt: Optional[str] = None


class QuestionSetDetail(BaseModel):
    id: Optional[str] = None
    user_id: Optional[str] = None
    collection_id: Optional[str] = None
    name: Optional[str] = None
    description: Optional[str] = None
    questions: Optional[list[Question]] = None
    gmt_created: Optional[datetime] = None
    gmt_updated: Optional[datetime] = None


class QuestionSetUpdate(BaseModel):
    name: Optional[str] = None
    description: Optional[str] = None


class Question1(BaseModel):
    question_text: str
    ground_truth: str
    question_type: Optional[QuestionType] = None


class QuestionsAdd(BaseModel):
    questions: list[Question1]


class QuestionUpdate(BaseModel):
    question_text: Optional[str] = None
    ground_truth: Optional[str] = None
    question_type: Optional[QuestionType] = None


class EvaluationStatus(
    RootModel[Literal['PENDING', 'RUNNING', 'PAUSED', 'COMPLETED', 'FAILED']]
):
    root: Literal['PENDING', 'RUNNING', 'PAUSED', 'COMPLETED', 'FAILED'] = Field(
        ..., description='Evaluation task lifecycle status'
    )


class Evaluation(BaseModel):
    id: Optional[str] = None
    user_id: Optional[str] = None
    name: Optional[str] = None
    collection_id: Optional[str] = None
    question_set_id: Optional[str] = None
    agent_llm_config: Optional[LLMConfig] = None
    judge_llm_config: Optional[LLMConfig] = None
    status: Optional[EvaluationStatus] = None
    error_message: Optional[str] = None
    total_questions: Optional[int] = None
    completed_questions: Optional[int] = None
    average_score: Optional[float] = None
    gmt_created: Optional[datetime] = None
    gmt_updated: Optional[datetime] = None


class EvaluationList(BaseModel):
    items: Optional[list[Evaluation]] = None
    total: Optional[int] = None
    page: Optional[int] = None
    page_size: Optional[int] = None


class EvaluationCreate(BaseModel):
    name: str
    collection_id: str
    question_set_id: str
    agent_llm_config: LLMConfig
    judge_llm_config: LLMConfig


class EvaluationItemStatus(
    RootModel[Literal['PENDING', 'RUNNING', 'COMPLETED', 'FAILED']]
):
    root: Literal['PENDING', 'RUNNING', 'COMPLETED', 'FAILED'] = Field(
        ..., description='Evaluation item lifecycle status'
    )


class EvaluationItem(BaseModel):
    id: Optional[str] = None
    evaluation_id: Optional[str] = None
    question_id: Optional[str] = None
    status: Optional[EvaluationItemStatus] = None
    question_text: Optional[str] = None
    ground_truth: Optional[str] = None
    rag_answer: Optional[str] = None
    rag_answer_details: Optional[dict[str, Any]] = None
    llm_judge_score: Optional[int] = None
    llm_judge_reasoning: Optional[str] = None
    gmt_created: Optional[datetime] = None
    gmt_updated: Optional[datetime] = None


class Config1(BaseModel):
    collection_id: Optional[str] = None
    question_set_id: Optional[str] = None
    agent_llm_config: Optional[dict[str, Any]] = None
    judge_llm_config: Optional[dict[str, Any]] = None


class EvaluationDetail(BaseModel):
    id: Optional[str] = None
    name: Optional[str] = None
    collection_name: Optional[str] = None
    question_set_name: Optional[str] = None
    status: Optional[EvaluationStatus] = None
    average_score: Optional[float] = None
    config: Optional[Config1] = None
    items: Optional[list[EvaluationItem]] = None
    gmt_created: Optional[datetime] = None


class EvaluationChatWithAgentRequest(BaseModel):
    collection_id: str
    agent_llm_config: LLMConfig
    question_text: str
    language: Optional[str] = None


class ChatSuccessResponse(BaseModel):
    messages: Optional[list[ChatMessage]] = None


class AgentErrorResponse(BaseModel):
    type: Optional[Literal['error']] = Field(
        None, description="The type of the response, must be 'error'."
    )
    id: Optional[str] = None
    data: Optional[str] = Field(None, description='Error message')
    timestamp: Optional[int] = None


class EvaluationChatWithAgentResponse(
    RootModel[Union[ChatSuccessResponse, AgentErrorResponse]]
):
    root: Union[ChatSuccessResponse, AgentErrorResponse]


class AgentMessage(BaseModel):
    """
    Message format for agent-type bots with additional capabilities
    """

    query: str = Field(
        ..., description='User query', examples=['Tell me about ApeRAG features']
    )
    collections: list[Collection] = Field(
        ...,
        description='List of collection objects to search in',
        examples=[
            [
                {'id': 'col_123', 'title': 'Example Collection'},
                {'id': 'col_456', 'title': 'Another Collection'},
            ]
        ],
    )
    completion: Optional[ModelSpec] = Field(
        None,
        description='Model specification for completion including provider and model details',
    )
    web_search_enabled: Optional[bool] = Field(
        False, description='Whether to enable web search', examples=[True]
    )
    language: Optional[
        Literal[
            'en-US',
            'zh-CN',
            'zh-TW',
            'ja-JP',
            'ko-KR',
            'fr-FR',
            'de-DE',
            'es-ES',
            'it-IT',
            'pt-BR',
            'ru-RU',
        ]
    ] = Field(
        'en-US', description='Language preference for the response', examples=['en-US']
    )
    files: Optional[list[File]] = None
